﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_IMAGE_UTILS_H
#define _UXS_IMAGE_UTILS_H

#pragma once

#include "ColorFormats.h"

namespace UX_STL
{
namespace UXMathExt
{
	

/*
=================================================
	выравнивает размер изображения в пикселях
=================================================
*/
	template <typename T, uint8 I, typename B>
	inline TVec<T,I> AlignImage (const TVec<T,I> &size, const B& align)
	{
		TypeDescriptor::MustBeInteger<T>();
		ASSERT( IsPowerOfTwo( align ) and "align must be a power of 2" );

		TVec<T,I>	res;
		uint		align_po2 = PowerOfTwo( align );

		FOR( i, res ) {
			res[i] = ( size[i] + (align-1) ) >> align_po2;
		}
		return res;
	}

/*
=================================================
	определяет номер уровня детализации текстуры
=================================================
*/
	template <typename T>
	inline bool GetMipmapLevel (const TVec<T,2> &dim1, const TVec<T,2> &dim2, int &iNumb)
	{
		TypeDescriptor::MustBeInteger<T>();

		if ( dim1.x % dim2.x != 0 and dim1.y % dim2.y != 0 )
			return false;	// not proportional

		if ( not IsPowerOfTwo(dim1.x) or not IsPowerOfTwo(dim1.y) or not IsPowerOfTwo(dim2.x) or not IsPowerOfTwo(dim2.y) )
			return false;	// not power of two

		int	x = PowerOfTwo(dim1.x) - PowerOfTwo(dim2.x),
			y = PowerOfTwo(dim1.y) - PowerOfTwo(dim2.y);

		if ( x != y )
			return false;	// not equal

		iNumb = x;
		return true;
	}
	
	template <typename T>
	inline bool GetMipmapLevel (const TVec<T,3> &dim1, const TVec<T,3> &dim2, int &iNumb)
	{
		TypeDescriptor::MustBeInteger<T>();

		if ( dim1.x % dim2.x != 0 and dim1.y % dim2.y != 0 and dim1.z % dim2.z != 0 )
			return false;	// not proportional

		if ( not IsPowerOfTwo(dim1.x) or not IsPowerOfTwo(dim1.y) or not IsPowerOfTwo(dim1.z) or
			 not IsPowerOfTwo(dim2.x) or not IsPowerOfTwo(dim2.y) or not IsPowerOfTwo(dim2.z) )
			return false;	// not power of two

		int	x = PowerOfTwo(dim1.x) - PowerOfTwo(dim2.x),
			y = PowerOfTwo(dim1.y) - PowerOfTwo(dim2.y),
			z = PowerOfTwo(dim1.z) - PowerOfTwo(dim2.z);

		if ( x != y or x != z )
			return false;	// not equal

		iNumb = x;
		return true;
	}
	
/*
=================================================
	возвращает количество мип уровней для
	заданного разрешения текстуры
	(для получения общего количества уровней
	 необходимо прибавить 1 - максимальный уровень)
=================================================
*/
	template <typename T>
	inline uint GetNumberOfMipmaps (const T len)
	{
		return PowerOfTwo( len );
	}
	
	template <typename T>
	inline uint GetNumberOfMipmaps (const TVec<T,2> &dim)
	{
		return PowerOfTwo( Max( dim.x, dim.y ) );
	}
	
	template <typename T>
	inline uint GetNumberOfMipmaps (const TVec<T,3> &dim)
	{
		return PowerOfTwo( Max( dim.x, dim.y, dim.z ) );
	}

/*
=================================================
	возвращает текстурные координаты кадра
=================================================
*/
	template <typename T, typename B>
	inline void GetTextureFrame (OUT TRect<T> &sTexcoord, uint uFrameIdx, const TVec<B,2> &vDim,
								 const TVec<B,2> &vNumFrames, const TVec<B,2> &border = TVec<B,2>())
	{
		TypeDescriptor::MustBeInteger<B>();
		ASSERT( uFrameIdx < vNumFrames.Area() );

		const TVec<B,2>		v_frame_size	= vDim / vNumFrames;
		const TVec<B,2>		v_frame_pos		= TVec<B,2>( uFrameIdx % vNumFrames.x, uFrameIdx / vNumFrames.x );
		TRect<B>			s_frame_coord	= TRect<B>( TVec<B,2>(0), v_frame_size ) + (v_frame_pos * v_frame_size);

		s_frame_coord.LeftBottom() += border;
		s_frame_coord.RightTop()   -= border;

		sTexcoord = s_frame_coord.template To< TRect<T> >() / vDim.template To< TVec<T,2> >();
	}

/*
=================================================
	возвращает размер изображения в байтах
	с учетом выравнивания
=================================================
*/
	template <typename T>
	inline usize AlignedImageDataSize (const TVec<T,3> &dimension, Bits bitPerPixel,
										Bytes rowAlign = Bytes(1), Bytes sliceAlign = Bytes(1))
	{
		TypeDescriptor::MustBeInteger<T>();
		const svec3	dim			= Max( dimension, TVec<T,3>(1) ).template To<svec3>();
		const usize	row_size	= AlignedImageRowSize( dim.x, bitPerPixel, rowAlign );
		const usize slice_size	= AlignedImageRowSize( dim.y * row_size, Bytes(1).ToBits(), sliceAlign );
		const usize	size		= slice_size * dim.z;
		return size;
	}

	template <typename ColorType, typename T>
	inline usize AlignedImageDataSize (const TVec<T,3> &dimension, Bytes rowAlign, Bytes sliceAlign)
	{
		return AlignedImageDataSize( dimension, Bits::SizeOf<ColorType>(), rowAlign, sliceAlign );
	}
	
/*
=================================================
	возвращает размер строки памяти изображения
	с учетом выравнивания
=================================================
*/
	template <typename T>
	inline usize AlignedImageRowSize (const T rowSize, Bits bitPerPixel, Bytes rowAlign)
	{
		TypeDescriptor::MustBeInteger<T>();
		return Bits( Align( Max( rowSize, T(1) ) * bitPerPixel, rowAlign ) ).ToBytes();
	}
	
/*
=================================================
	возвращает размер блока памяти изображения
	с учетом выравнивания
=================================================
*/
	template <typename T>
	inline usize AlignedImageSliceSize (const TVec<T,2> dim, Bits bitPerPixel, Bytes rowAlign, Bytes sliceAlign)
	{
		const usize	row_size	= AlignedImageRowSize( dim.x, bitPerPixel, rowAlign );
		const usize slice_size	= AlignedImageRowSize( dim.y * row_size, Bytes(1).ToBits(), sliceAlign );
		return slice_size;
	}

/*
=================================================
	возвращает смещение пикселя в байтах
=================================================
*/
	template <typename T>
	inline usize GetPixelOffset (const TVec<T,3> &coord, const TVec<T,3> &dimension, Bytes bytePerPixel,
								 Bytes rowAlign = Bytes(1), Bytes sliceAlign = Bytes(1))
	{
		TypeDescriptor::MustBeInteger<T>();
		const svec3	dim			= Max( dimension, TVec<T,3>(1) ).template To<svec3>();
		const usize	row_size	= AlignedImageRowSize( dim.x, bytePerPixel.ToBits(), rowAlign );
		const usize slice_size	= AlignedImageRowSize( dim.y * row_size, Bytes(1).ToBits(), sliceAlign );
		const usize	z_off		= slice_size * coord.z;
		const usize	y_off		= z_off + row_size * coord.y;
		const usize	i			= y_off + bytePerPixel * coord.x;
		return i;
	}



	//
	// Image Utils
	//
	
	struct ImageUtils
	{
		template <typename T, typename SrcType, typename DstType>
		static bool Copy (const TVec<T,3> &dimension, const SrcType *srcImage, DstType *dstImage,
						  Bytes srcRowAlignInBytes = Bytes(1), Bytes dstRowAlignInBytes = Bytes(1))
		{
			return CopyPart( dimension, TVec<T,3>(0), srcImage, dimension, TVec<T,3>(0), dstImage, srcRowAlignInBytes, dstRowAlignInBytes );
		}


		template <typename T, typename SrcType, typename DstType>
		static bool CopyPart (const TVec<T,3> &srcDim, const TVec<T,3> &srcOff, const SrcType *srcImage,
							  const TVec<T,3> &dstDim, const TVec<T,3> &dstOff, DstType *dstImage,
							  Bytes srcRowAlignInBytes = Bytes(1), Bytes dstRowAlignInBytes = Bytes(1))
		{
			CHECK_ERR( srcImage != null and dstImage != null );

			const uvec3		src_dim	= Max( srcDim, TVec<T,3>(1) ).template To<uvec3>();
			const uvec3		src_off = srcOff.template To<uvec3>();
			const uvec3		dst_dim	= Max( dstDim, TVec<T,3>(1) ).template To<uvec3>();
			const uvec3		dst_off	= dstOff.template To<uvec3>();
			const usize		src_row	= AlignedImageRowSize( src_dim.x, Bits::SizeOf<SrcType>(), srcRowAlignInBytes );
			const usize		dst_row	= AlignedImageRowSize( dst_dim.x, Bits::SizeOf<DstType>(), dstRowAlignInBytes );
			const void *	src		= (const void *) srcImage;
			void *			dst		= (void *) dstImage;

			const uvec3		src_size = src_dim - src_off;
			const uvec3		dst_size = dst_dim - dst_off;
			
			//CHECK_ERR( All( src_size == dst_size ) );
			CHECK_ERR( All( src_off <= src_dim ) );
			CHECK_ERR( All( dst_off <= dst_dim ) );

			for (uint z = 0; z < dst_size.z and z < src_size.z; ++z)
			{
				const usize	src_z_off = src_row * src_dim.y * (z + src_off.z);
				const usize	dst_z_off = dst_row * dst_dim.y * (z + dst_off.z);

				for (uint y = 0; y < dst_size.y and y < src_size.y; ++y)
				{
					const usize	src_y_off = src_z_off + src_row * (y + src_off.y);
					const usize	dst_y_off = dst_z_off + dst_row * (y + dst_off.y);

					for (uint x = 0; x < dst_size.x and x < src_size.x; ++x)
					{
						const usize	i = src_y_off + sizeof(SrcType) * (x + src_off.x);
						const usize	j = dst_y_off + sizeof(DstType) * (x + dst_off.x);

						const SrcType *	s = MovePointer< SrcType >( src, i );
						DstType *		d = MovePointer< DstType >( dst, j );
						
						ColorFormat::ColorFormatConverter::Convert( *d, *s );
					}
				}
			}
			return true;
		}


		template <typename T, typename SrcType, typename DstType>
		static bool Fill (const TVec<T,3> &regionDim, const SrcType &color,
						  const TVec<T,3> &dstOff, const TVec<T,3> &dstDim, DstType *dstImage,
						  Bytes dstRowAlignInBytes = Bytes(1))
		{
			CHECK_ERR( dstImage != null );

			const uvec3		src_dim	= Max( regionDim, TVec<T,3>(1) ).template To<uvec3>();
			const uvec3		dst_dim	= Max( dstDim, TVec<T,3>(1) ).template To<uvec3>();
			const uvec3		dst_off	= dstOff.template To<uvec3>();
			const usize		dst_row	= AlignedImageRowSize( dst_dim.x, Bits::SizeOf<DstType>(), dstRowAlignInBytes );
			void *			dst		= (void *) dstImage;
			
			CHECK_ERR( All( src_dim <= dst_dim - dst_off ) );

			for (uint z = 0; z < src_dim.z; ++z)
			{
				const usize	dst_z_off = dst_row * dst_dim.y * (z + dst_off.z);

				for (uint y = 0; y < src_dim.y; ++y)
				{
					const usize	dst_y_off = dst_z_off + dst_row * (y + dst_off.y);

					for (uint x = 0; x < src_dim.x; ++x)
					{
						const usize	j = dst_y_off + sizeof(DstType) * (x + dst_off.x);
						DstType *	d = MovePointer< DstType >( dst, j );
						
						ColorFormat::ColorFormatConverter::Convert( *d, color );
					}
				}
			}
			return true;
		}


		template <typename Iterator, typename T, typename ColorType>
		static bool ForEach (Iterator &iter, const TVec<T,3> &regionDim, ColorType *image,
							 const TVec<T,3> &offset, const TVec<T,3> &dim, Bytes dstRowAlignInBytes = Bytes(1))
		{
			CHECK_ERR( image != null );

			const uvec3		src_dim	= Max( regionDim, TVec<T,3>(1) ).template To<uvec3>();
			const uvec3		dst_dim	= Max( dim, TVec<T,3>(1) ).template To<uvec3>();
			const uvec3		dst_off	= offset.template To<uvec3>();
			const usize		dst_row	= AlignedImageRowSize( dst_dim.x, Bits::SizeOf<ColorType>(), dstRowAlignInBytes );
			void *			dst		= (void *) image;
			
			CHECK_ERR( All( src_dim <= dst_dim - dst_off ) );

			for (uint z = 0; z < src_dim.z; ++z)
			{
				const usize	dst_z_off = dst_row * dst_dim.y * (z + dst_off.z);

				for (uint y = 0; y < src_dim.y; ++y)
				{
					const usize	dst_y_off = dst_z_off + dst_row * (y + dst_off.y);

					for (uint x = 0; x < src_dim.x; ++x)
					{
						const usize	j = dst_y_off + sizeof(ColorType) * (x + dst_off.x);
						ColorType *	d = MovePointer< ColorType >( dst, j );
						
						iter( *d, uvec3(x,y,z) );
					}
				}
			}
			return true;
		}


		template <typename T, typename ColorType>
		static ColorType & GetPixel (const TVec<T,3> &imageDim, ColorType *image, const TVec<T,3> &pixelCoord,
									 uint rowAlignInBytes = 1)
		{
			ASSUME( image != null );
			
			const uvec3		dim		= Max( imageDim, TVec<T,3>(1) ).template To<uvec3>();
			const uvec3		coord	= pixelCoord.template To<uvec3>();
			const usize		row		= AlignedImageRowSize( dim.x, Bits::SizeOf<ColorType>(), rowAlignInBytes );
			const usize		i		= row * (dim.y * coord.z + coord.y) + coord.x * sizeof(ColorType);
			
			ASSERT( All( coord < dim ) );

			return *MovePointer< ColorType *>( image, i );
		}
	};


//-------------------------------------------------------------------

}	// UXMathExt
}	// UX_STL

#endif	// _UXS_IMAGE_UTILS_H